#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/partitions.h>
#include <linux/vmalloc.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/ktime.h>
#include <linux/scatterlist.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <mach/board-a320.h>
#include <mach/ftscu010.h>
#include <mach/ftdmac020.h>
#include <mach/dma-a369.h>

#include "ftnandc024v2.1_nand.h"

#if defined(CONFIG_FTNANDC024_USE_AHBDMA)
static void ftnandc024_dma_callback(void *param) {
	struct ftnandc024_nand_data *data = (struct ftnandc024_nand_data *)param;
	data->dma_sts = DMA_COMPLETE;
	wake_up_interruptible(&data->dma_wait_queue);
}
	
static void ftnandc024_dma_wait(struct mtd_info *mtd, u8 dir) {
	struct nand_chip *chip = mtd->priv;
	struct ftnandc024_nand_data *data = chip->priv;
	int wait_queue_status;
	
	wait_queue_status = wait_event_interruptible_timeout(data->dma_wait_queue, 
						data->dma_sts == DMA_COMPLETE, HZ);
	if(wait_queue_status == 0) {
		if(dir == DMA_TO_DEVICE)
			printk(KERN_ERR "Timeout(DMA write)PG:0x%x\n", data->page_addr);
		else if(dir == DMA_FROM_DEVICE)
			printk(KERN_ERR "Timeout(DMA read) PG:0x%x\n", data->page_addr);
	}
}

int ftnandc024_nand_dma_wr(struct mtd_info *mtd, const uint8_t *buf) {
	
	struct nand_chip *chip = mtd->priv;
	struct ftnandc024_nand_data *data = chip->priv;
	struct dma_chan *chan = data->dma_chan;
	struct scatterlist sg;
	struct dma_async_tx_descriptor *desc;
	dma_addr_t dma_addr;

	data->dma_sts = DMA_ONGOING;
	// buf isn't from kmalloc or get_free_page(s)
	if(!virt_addr_valid(buf)) {
		DBGLEVEL2(ftnandc024_dbg("Buf isn't from kmalloc or get_free_page(s)\n"));
		memcpy(data->dma_buf, buf, mtd->writesize);
		sg_init_one(&sg, dma_to_virt(data->dev, data->dmaaddr), mtd->writesize);
		sg_dma_address(&sg) = data->dmaaddr;
		data->slave.common.direction = DMA_TO_DEVICE;
		data->slave.common.src_addr = (dma_addr_t)data->dmaaddr;
		data->slave.common.dst_addr = (dma_addr_t)data->chip.IO_ADDR_R;
		data->slave.common.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
		data->slave.common.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
		data->slave.handshake = A369_DMAC_HANDSHAKE_EDMACH0;
		
		desc = chan->device->device_prep_slave_sg(chan, &sg, 1, DMA_TO_DEVICE,		 
			DMA_PREP_INTERRUPT | DMA_CTRL_ACK | DMA_COMPL_SKIP_SRC_UNMAP 
			| DMA_COMPL_SKIP_DEST_UNMAP);
		if (desc == NULL) {
			printk("Desc preg is failed\n");
			return 1;
		}
		desc->callback = ftnandc024_dma_callback;
		desc->callback_param = data; 
		// Start Tx
		desc->tx_submit(desc);
		chan->device->device_issue_pending(chan);
		// Wait for completion of DMA engine	
		ftnandc024_dma_wait(mtd, DMA_TO_DEVICE);
	}
	else {
		DBGLEVEL2(ftnandc024_dbg("Buf is from kmalloc or get_free_page(s)\n"));
		dma_addr = dma_map_single( data->dev, (void *)buf, mtd->writesize, DMA_TO_DEVICE);	
		if(dma_mapping_error(data->dev, dma_addr)) {	
			printk(KERN_ERR "dma_map_single(w) is failed\n");
			dma_unmap_single(data->dev, dma_addr, mtd->writesize, DMA_TO_DEVICE);
			return 1;
		}
		sg_init_one(&sg, (void *)buf, mtd->writesize);
		sg_dma_address(&sg) = dma_addr;
		data->slave.common.direction = DMA_TO_DEVICE;
		data->slave.common.src_addr = dma_addr;
		data->slave.common.dst_addr = (dma_addr_t)data->chip.IO_ADDR_R;
		data->slave.common.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
		data->slave.common.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
		data->slave.handshake = A369_DMAC_HANDSHAKE_EDMACH0;
		
		desc = chan->device->device_prep_slave_sg(chan, &sg, 1, DMA_TO_DEVICE,		 
			DMA_PREP_INTERRUPT | DMA_CTRL_ACK | DMA_COMPL_SKIP_SRC_UNMAP 
			| DMA_COMPL_SKIP_DEST_UNMAP);
		if (desc == NULL) {
			printk("Desc prep(w) is failed\n");
			dma_unmap_single(data->dev, dma_addr, mtd->writesize, DMA_TO_DEVICE);
			return 1;
		}
		desc->callback = ftnandc024_dma_callback;
		desc->callback_param = data; 
		// Start Tx
		desc->tx_submit(desc);
		chan->device->device_issue_pending(chan);
		// Wait for completion of DMA engine	
		ftnandc024_dma_wait(mtd, DMA_TO_DEVICE);
		dma_unmap_single(data->dev, dma_addr, mtd->writesize, DMA_TO_DEVICE);
		
	}
	return 0;
}

int ftnandc024_nand_dma_rd(struct mtd_info *mtd, uint8_t *buf) {

	struct nand_chip *chip = mtd->priv;
	struct ftnandc024_nand_data *data = chip->priv;
	struct dma_chan *chan = data->dma_chan;
	struct scatterlist sg;
	struct dma_async_tx_descriptor *desc;
	dma_addr_t dma_addr;

	data->dma_sts = DMA_ONGOING;
	
	// buf isn't from kmalloc or get_free_page(s)
	if(!virt_addr_valid(buf)) {
		DBGLEVEL2(ftnandc024_dbg("Buf isn't from kmalloc or get_free_page(s)\n"));
		sg_init_one(&sg, dma_to_virt(data->dev, data->dmaaddr), mtd->writesize);
		sg_dma_address(&sg) = data->dmaaddr;
		data->slave.common.direction = DMA_FROM_DEVICE;
		data->slave.common.src_addr = (dma_addr_t)data->chip.IO_ADDR_R;
		data->slave.common.dst_addr = (dma_addr_t)data->dmaaddr;
		data->slave.common.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
		data->slave.common.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
		data->slave.handshake = A369_DMAC_HANDSHAKE_EDMACH0;
		
		desc = chan->device->device_prep_slave_sg(chan, &sg, 1, DMA_FROM_DEVICE,		 
			DMA_PREP_INTERRUPT | DMA_CTRL_ACK | DMA_COMPL_SKIP_SRC_UNMAP 
			| DMA_COMPL_SKIP_DEST_UNMAP);
		if (desc == NULL) {
			printk("Desc preg is failed\n");
			//goto out;
			return 1;
		}
		desc->callback = ftnandc024_dma_callback;
		desc->callback_param = data; 
		// Start Tx
		desc->tx_submit(desc);
		chan->device->device_issue_pending(chan);
		// Wait for completion of DMA engine	
		ftnandc024_dma_wait(mtd, DMA_FROM_DEVICE);
		memcpy(buf, data->dma_buf, mtd->writesize);
	}
	else {
		DBGLEVEL2(ftnandc024_dbg("Buf is from kmalloc or get_free_page(s)\n"));
		dma_addr = dma_map_single( data->dev, (void *)buf, mtd->writesize, DMA_FROM_DEVICE);	
		if(dma_mapping_error(data->dev, dma_addr)) {
			printk(KERN_ERR "dma_map_single(r) is failed\n");
			dma_unmap_single(data->dev, dma_addr, mtd->writesize, DMA_FROM_DEVICE);
			//goto out;
			return 1;
		}
		sg_init_one(&sg, (void *)buf, mtd->writesize);
		sg_dma_address(&sg) = dma_addr;
		data->slave.common.direction = DMA_FROM_DEVICE;
		data->slave.common.src_addr = (dma_addr_t)data->chip.IO_ADDR_R;
		data->slave.common.dst_addr = dma_addr;
		data->slave.common.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
		data->slave.common.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
		data->slave.handshake = A369_DMAC_HANDSHAKE_EDMACH0;
		
		desc = chan->device->device_prep_slave_sg(chan, &sg, 1, DMA_FROM_DEVICE,		 
			DMA_PREP_INTERRUPT | DMA_CTRL_ACK | DMA_COMPL_SKIP_SRC_UNMAP 
			| DMA_COMPL_SKIP_DEST_UNMAP);
		if (desc == NULL) {
			dma_unmap_single(data->dev, dma_addr, mtd->writesize, DMA_FROM_DEVICE);
			printk("Desc preg(r) is failed\n");
			//goto out;
			return 1;
		}
		desc->callback = ftnandc024_dma_callback;
		desc->callback_param = data; 
		// Start Tx
		desc->tx_submit(desc);
		chan->device->device_issue_pending(chan);
		// Wait for completion of DMA engine	
		ftnandc024_dma_wait(mtd, DMA_FROM_DEVICE);
		dma_unmap_single(data->dev, dma_addr, mtd->writesize, DMA_FROM_DEVICE);
	}
	return 0;
}
#endif
